# coding=utf8
import ctypes

from pyjvm.instructions.common import ClassInitializer
from pyjvm.instructions.invoke import register, invoke_method
from pyjvm.runtime.heap import JObject, Heap
from pyjvm.runtime.metadata import Method
from pyjvm.runtime.thread import Frame, OperandStack


# sun.io

class Win32ErrorMode:

    @staticmethod
    def setErrorMode(frame: Frame):
        frame.operand_stack.push(0)


register("sun/io/Win32ErrorMode", "setErrorMode", "(J)J", Win32ErrorMode.setErrorMode)


# sun.misc
class VM:

    @staticmethod
    def initialize(frame: Frame):
        loader = frame.method.klass.loader
        sys_class = loader.load_class('java/lang/System')
        init_class = sys_class.get_static_method('initializeSystemClass', '()V')
        invoke_method(frame, init_class)


register("sun/misc/VM", "initialize", "()V", VM.initialize)


class Unsafe:
    NAME = 'sun/misc/Unsafe'

    @staticmethod
    def arrayBaseOffset(frame: Frame):
        frame.operand_stack.push(0)  # todo

    @staticmethod
    def arrayIndexScale(frame: Frame):
        frame.operand_stack.push(1)  # todo

    @staticmethod
    def addressSize(frame: Frame):
        # address = frame.local_vars[1]
        frame.operand_stack.push(8)

    @staticmethod
    def objectFieldOffset(frame: Frame):
        j_field = frame.local_vars[1]
        offset = j_field.get_field_value('slot', 'I')
        frame.operand_stack.push(offset)

    @staticmethod
    def compareAndSwap(frame: Frame):
        obj = frame.local_vars[1]
        offset = frame.local_vars[2]
        expected = frame.local_vars[4]
        new_val = frame.local_vars[5]

        swapped = Unsafe.__cas(obj, offset, expected, new_val)
        frame.operand_stack.push(swapped)

    @staticmethod
    def compareAndSwapLong(frame: Frame):
        obj = frame.local_vars[1]
        offset = frame.local_vars[2]
        expected = frame.local_vars[4]
        new_val = frame.local_vars[6]

        swapped = Unsafe.__cas(obj, offset, expected, new_val)
        frame.operand_stack.push(swapped)

    @staticmethod
    def __cas(obj, offset, expected, new_val):
        cur_val = obj.data[offset]
        if cur_val == expected:
            obj.data[offset] = new_val
            return True
        return False

    @staticmethod
    def get(frame: Frame):
        obj = frame.local_vars[1]
        offset = frame.local_vars[2]
        frame.operand_stack.push(obj.data[offset])

    __memory = {}

    @staticmethod
    def allocate(size):
        block = MemoryBlock(size)
        address = id(block)
        Unsafe.__memory[address] = block
        return address

    @staticmethod
    def reallocate(address):
        memory = Unsafe.getMemory(address)
        new_address = Unsafe.allocate(memory.size)
        Unsafe.free(address)
        return new_address

    @staticmethod
    def getMemoryVal(address):
        return Unsafe.getMemory(address).val

    @staticmethod
    def getMemorySize(address):
        return Unsafe.getMemory(address).size

    @staticmethod
    def getMemory(address):
        return Unsafe.__memory.get(address)

    @staticmethod
    def setMemoryVal(address, val):
        Unsafe.__memory[address].val = val

    @staticmethod
    def allocateMemory(frame: Frame):
        size = frame.local_vars[1]
        frame.operand_stack.push(Unsafe.allocate(size))

    @staticmethod
    def reallocateMemory(frame: Frame):
        address = frame.local_vars[1]
        frame.operand_stack.push(Unsafe.reallocate(address))

    @staticmethod
    def free(address):
        address = Unsafe.__memory.pop(address)
        address.val = None
        address.size = 0

    @staticmethod
    def freeMemory(frame: Frame):
        address = frame.local_vars[1]
        Unsafe.free(address)

    @staticmethod
    def memoryValFrom(frame: Frame):
        address = frame.local_vars[1]
        val = Unsafe.getMemoryVal(address)
        return val

    @staticmethod
    def memoryGet(frame: Frame):
        frame.operand_stack.push(Unsafe.memoryValFrom(frame))

    @staticmethod
    def memoryPut(frame: Frame):
        address = frame.local_vars[1]
        val = frame.local_vars[3]
        memory = Unsafe.getMemory(address)
        memory.val = val

    @staticmethod
    def getByte(frame: Frame):
        val = Unsafe.memoryValFrom(frame)
        b_val = ctypes.c_byte(val).value
        frame.operand_stack.push(b_val)


class MemoryBlock:  # hack

    def __init__(self, size=None):
        self.val = None
        self.size = size


register(Unsafe.NAME, "arrayBaseOffset", "(Ljava/lang/Class;)I", Unsafe.arrayBaseOffset)
register(Unsafe.NAME, "arrayIndexScale", "(Ljava/lang/Class;)I", Unsafe.arrayIndexScale)
register(Unsafe.NAME, "addressSize", "()I", Unsafe.addressSize)
register(Unsafe.NAME, "objectFieldOffset", "(Ljava/lang/reflect/Field;)J", Unsafe.objectFieldOffset)
register(Unsafe.NAME, "compareAndSwapObject", "(Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z",
         Unsafe.compareAndSwap)
register(Unsafe.NAME, "getIntVolatile", "(Ljava/lang/Object;J)I", Unsafe.get)
register(Unsafe.NAME, "compareAndSwapInt", "(Ljava/lang/Object;JII)Z", Unsafe.compareAndSwap)
register(Unsafe.NAME, "getObjectVolatile", "(Ljava/lang/Object;J)Ljava/lang/Object;", Unsafe.get)
register(Unsafe.NAME, "compareAndSwapLong", "(Ljava/lang/Object;JJJ)Z", Unsafe.compareAndSwapLong)

register(Unsafe.NAME, "allocateMemory", "(J)J", Unsafe.allocateMemory)
register(Unsafe.NAME, "reallocateMemory", "(JJ)J", Unsafe.reallocateMemory)
register(Unsafe.NAME, "freeMemory", "(J)V", Unsafe.freeMemory)
register(Unsafe.NAME, "putLong", "(JJ)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putByte", "(JB)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putShort", "(JS)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putChar", "(JC)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putInt", "(JI)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putFloat", "(JF)V", Unsafe.memoryPut)
register(Unsafe.NAME, "putDouble", "(JD)V", Unsafe.memoryPut)
register(Unsafe.NAME, "getByte", "(J)B", Unsafe.getByte)
register(Unsafe.NAME, "getShort", "(J)S", Unsafe.memoryGet)
register(Unsafe.NAME, "getChar", "(J)C", Unsafe.memoryGet)
register(Unsafe.NAME, "getInt", "(J)I", Unsafe.memoryGet)
register(Unsafe.NAME, "getLong", "(J)J", Unsafe.memoryGet)
register(Unsafe.NAME, "getFloat", "(J)F", Unsafe.memoryGet)
register(Unsafe.NAME, "getDouble", "(J)D", Unsafe.memoryGet)


class Reflection:
    NAME = 'sun/reflect/Reflection'

    @staticmethod
    def getCallerClass(frame: Frame):
        caller_frame = frame.thread.top_frame(2)
        caller_class = caller_frame.method.klass.j_class
        frame.operand_stack.push(caller_class)

    @staticmethod
    def getClassAccessFlags(frame: Frame):
        _type = frame.local_vars[0]
        klass = _type.extra
        frame.operand_stack.push(klass.access_flags)


register(Reflection.NAME, "getCallerClass", "()Ljava/lang/Class;", Reflection.getCallerClass)
register(Reflection.NAME, "getClassAccessFlags", "(Ljava/lang/Class;)I", Reflection.getClassAccessFlags)


def getMethod(method_obj: JObject, is_constructor: bool):
    extra = method_obj.extra
    if extra is not None:
        return extra

    if is_constructor:
        root = method_obj.get_field_value('root', 'Ljava/lang/reflect/Constructor;')
        return root.extra
    else:
        root = method_obj.get_field_value('root', 'Ljava/lang/reflect/Method;')
        return root.extra


def getConstructor(constructor_obj):
    return getMethod(constructor_obj, True)


def convertArgs(obj: JObject, arg_arr_obj: JObject, method: Method):
    ops = OperandStack(method.args_slot_count)
    if not method.is_static:
        ops.push(obj)
    return ops


class NativeConstructorAccessorImpl:
    NAME = 'sun/reflect/NativeConstructorAccessorImpl'

    @staticmethod
    def newInstance0(frame: Frame):
        constructor_obj = frame.local_vars[0]
        args_obj = frame.local_vars[1]

        constructor: Method = getConstructor(constructor_obj)  # j obj => Method
        klass = constructor.klass

        if not klass.inited:
            frame.revert_next_pc()
            ClassInitializer.init_class(frame.thread, klass)
            return

        obj = Heap.new_object(klass)
        frame.operand_stack.push(obj)

        ops = convertArgs(obj, args_obj, constructor)
        shim_frame = frame.thread.new_shim_frame(ops)
        invoke_method(shim_frame, constructor)


register(NativeConstructorAccessorImpl.NAME, "newInstance0",
         "(Ljava/lang/reflect/Constructor;[Ljava/lang/Object;)Ljava/lang/Object;",
         NativeConstructorAccessorImpl.newInstance0)


class Signal:
    NAME = 'sun/misc/Signal'

    @staticmethod
    def findSignal(frame: Frame):
        # name = frame.local_vars[0]
        frame.operand_stack.push(0)

    @staticmethod
    def handle0(frame: Frame):
        # var1 = frame.local_vars[0]
        # var2 = frame.local_vars[1]
        frame.operand_stack.push(0)


register(Signal.NAME, "findSignal", "(Ljava/lang/String;)I", Signal.findSignal)
register(Signal.NAME, "handle0", "(IJ)J", Signal.handle0)
